package com.je.core.lock;

import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCommands;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 单节点Redis锁
 *
 * @author wangmm@ketr.com.cn
 * @date 2020/12/16
 */
@Component
public class RedisSingleLock {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

//    /**
//     * 加锁脚本
//     */
//    private static final String LOCK =
//            "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then \n" +
//                    "    redis.call('expire',KEYS[1],ARGV[2]) \n" +
//                    "    return 1 \n" +
//                    "else \n" +
//                    "    return 0 \n" +
//                    "end";

    /**
     * 解锁脚本
     */
    private static final String UNLOCK = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

    @Resource
    protected StringRedisTemplate redisTemplate;

    /**
     * 阻塞获取锁
     *
     * @param key         锁标识
     * @param value       加锁时的value
     * @param timeout     超时时间
     * @param retries     重试次数
     * @param waitingTime 每次重试时间间隔
     * @return 是否成功
     * @throws InterruptedException
     */
    public Boolean tryLock(String key, String value, long timeout, int retries, long waitingTime) throws InterruptedException {
        do {
            //获取锁
            Boolean result = lock(key, value, timeout);
            if (result) {
                return true;
            }
            if (retries > 0) {
                //重试延时
                TimeUnit.MILLISECONDS.sleep(waitingTime);
            }
            if (Thread.currentThread().isInterrupted()) {
                //当前线程中断时结束重试
                break;
            }
        } while (retries-- > 0);
        return false;
    }

    /**
     * 加锁
     *
     * @param key     锁标识
     * @param value   加锁时的value
     * @param timeout 超时时间
     * @return 是否成功
     */
    public Boolean lock(String key, String value, long timeout) {
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            //TODO 由于版本过低，使用 Commands 直接执行加锁命令。
            JedisCommands commands = (JedisCommands) connection.getNativeConnection();
            //SET key value NX PX timeout
            String result = commands.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, timeout);
            return LOCK_SUCCESS.equals(result);
        });
    }

    /**
     * 解锁
     *
     * @param key   锁标识
     * @param value 加锁时的value
     * @return 是否成功
     */
    public Boolean unLock(String key, String value) {
        Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
            Jedis jedis = (Jedis) connection.getNativeConnection();
            //redisTemplate.execute执行lua有问题，采用Jedis的形式
            return (Long) jedis.eval(UNLOCK, 1, key, value);
        });
        return result != null && result > 0;
    }
}